package com.conversationboard.view;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;

import com.conversationboard.authenticator.Obfuscator;
import com.conversationboard.config.Configuration;
import com.conversationboard.formatter.DateFormatter;
import com.conversationboard.formatter.Formatter;
import com.conversationboard.formatter.ImageHider;
import com.conversationboard.model.Message;
import com.conversationboard.model.Messages;
import com.conversationboard.model.Role;
import com.conversationboard.model.Thread;
import com.conversationboard.model.User;
import com.conversationboard.network.UserLocation;
import com.conversationboard.site.Site;

public class MessagesDisplay {

	private static long TWO_WEEKS_IN_MILLISECONDS = 1209600000;
	private static final int MESSAGE_GAP_THRESHOLD = 5;


	/**
	 * Show the contents of the thread
	 * 
	 * @param thread the thread to display
	 * @param threadViewerUserId the user ID of the thread viewer
	 * @param showAllMessages whether to show all messages, or just a subset
	 * @return the HTML representation of the message for display
	 * @throws SQLException
	 */

	public static String display(HttpServletRequest request, Thread thread, String threadViewerUserId, boolean showAllMessages, int originalMessageCount, int bookmarkedMessageId, String[] bbCodeErrorContainer, boolean mobileDevice) throws SQLException, UnsupportedEncodingException {

		StringBuilder buffer = new StringBuilder();
		boolean admin = false;

		String key = URLEncoder.encode((Obfuscator.encrypt(request.getSession().getId())), "UTF-8");

		/* Get the user viewing the thread. */

		User user = User.get(threadViewerUserId);

		if (user.isBoardAdmin(thread.getBoardId())) {
			admin = true;
		}

		if (admin) {
			displayThreadAdminLinks(thread, buffer, user);
		}

		/* Record the fact we're looking at the entire thread for the sake of stats */

		if (showAllMessages) {
			Site.incrementShowAllMessagesHitCount();
		}

		appendPoll(key, thread, bookmarkedMessageId, buffer, user, mobileDevice);

		/* If it exists, get the most recent message the viewer has viewed on this thread. */

		boolean displayFromMostRecentlyRead = false;

		int mostRecentReadMessage = user.getMostRecentMessageId(thread.getBoardId(), thread.getThreadId());

		if (mostRecentReadMessage > 0) {
			displayFromMostRecentlyRead = true;
		}

		/* Check if the thread is deleted or locked */

		if (thread.isDeleted()) {
			buffer.append("<div class='formsection'><p><strong>This thread has been deleted.</strong></p></div>");
			return buffer.toString();
		}

		if (bbCodeErrorContainer != null) {
			appendBBCodeErrorContainer(bbCodeErrorContainer, buffer);
			return buffer.toString();
		}

		/* Create this form after checking conditions that may return early.  Else, there will be a dangling form. */

		buffer.append("<form action=\"" + Configuration.getInstance().getRoot() + "/Admin/AdminConfirmDeleteSelectedThreadsControllerServlet\" method=\"post\">");

		Messages messages = thread.getMessages();

		int showAtStart = Configuration.getInstance().getOldestMessageCount();
		int endMessagesStartAt = messages.size() - Configuration.getInstance().getNewestMessageCount();

		/* If you're logged in, then show the 'show all messages' link at the top. */

		if (displayShowAllMessagesLink(showAllMessages, bookmarkedMessageId, user, messages, showAtStart, endMessagesStartAt)) {
			appendShowAllMessagesLink(thread, buffer, messages);
		}

		/* If you're viewing on a mobile device stick in a link to the last message to let you jump there quickly */

		if (mobileDevice) {
			int lastMessageId = messages.getLastMessage().getMessageId();
			buffer.append("<p><a href='" + Configuration.getInstance().getRoot() + "/ThreadSelectedControllerServlet?boardid=" + thread.getBoardId() + "&amp;threadid=" + thread.getThreadId() + "#msg" + lastMessageId + "'>Jump to Most Recent Message" + "</a></p>");
		}

		int messageCount = 0;
		int lastMessageId = 0;
		Message previousMessage = null; /* Hold a reference to the previous message */
		boolean displayedPreviousPageLink = false;
		boolean displayingAllMessages = true;
		boolean justSkippedMessages = false;

		for (Message message : messages) {

			/* If you've sent in a bookmark ID, then don't display messages until this point. */

			if (message.getMessageId() < bookmarkedMessageId) {
				displayingAllMessages = false;
				continue;
			}

			/* If you didn't start at the beginning of the thread, then show a link to the previous page of messages */

			if ((bookmarkedMessageId > 0) && !displayedPreviousPageLink) {
				if (!displayingAllMessages) {

					/* If the link takes you back to the start, then just link direct to the thread (so you get poll info if it's a poll) */

					if (message.getPreviousPageMessageId() <= message.getParentThread().getFirstMessage().getMessageId()) {
						buffer.append("<div class='previouspage'><a href='");
						buffer.append(Configuration.getInstance().getRoot());
						buffer.append("/ThreadSelectedControllerServlet?boardid=");
						buffer.append(message.getBoardId());
						buffer.append("&amp;threadid=");
						buffer.append(message.getThreadId());
						buffer.append("#msg");
						buffer.append(message.getMessageId());
						buffer.append("'>View Previous Page...</a></div>");
					} else {
						buffer.append("<div class='previouspage'><a href='");
						buffer.append(Configuration.getInstance().getRoot());
						buffer.append("/ThreadSelectedControllerServlet?boardid=");
						buffer.append(message.getBoardId());
						buffer.append("&amp;threadid=");
						buffer.append(message.getThreadId());
						buffer.append("&amp;bookmarkedmessageid=");
						buffer.append(message.getPreviousPageMessageId());
						buffer.append("#msg");
						buffer.append(message.getMessageId());
						buffer.append("'>View Previous Page...</a></div>");
					}

					displayedPreviousPageLink = true;
				}
			}

			boolean xPost = (originalMessageCount > 0);

			if (displayThisMessage(showAllMessages, displayFromMostRecentlyRead, messageCount, showAtStart, endMessagesStartAt)) {

				/* If the message is deleted, and you're not an admin, don't display it. Deleted messages will mess
				 * with the sums we've done before this loop, so compensate by changing them. This won't work in the
				 * event that there are deleted messages within the last (x) messages, as you'd have to go over the message
				 * list twice to work that out, and it's just not worth it for a minority scenario. */

				if (!admin && message.isDeleted()) {

					messageCount++;

					if (messageCount <= showAtStart) {
						showAtStart++;
					}

					continue;
				}

				/* If it's a poll, don't show the first message, since the poll displays it anyway. Also compensate for this
				 * by incrementing the message count. */

				if (thread.isPoll() && (message.getMessageId() == thread.getFirstMessage().getMessageId())) {
					messageCount++;
					continue;
				}

				/* If this is displaying an x-post warning, skip all messages until you hit the first message you haven't yet seen. */

				if ((originalMessageCount > 0) && (messageCount < originalMessageCount)) {
					messageCount++;
					continue;
				}

				if (!justSkippedMessages) {
					appendThreadRevivalIndicator(buffer, previousMessage, message, user);
				} else {
					justSkippedMessages = false;
				}

				appendUnreadMessageLink(buffer, mostRecentReadMessage, message);
				appendPermalink(buffer, message);
				appendXPostWarning(originalMessageCount, buffer, messageCount);
				appendSpecialFormatting(buffer, message);

				String body = hideImages(mobileDevice, admin, user, message);

				buffer.append(body);

				appendUserDetails(key, buffer, thread, message, user, mobileDevice);
				appendAdminLinks(thread, buffer, admin, user, message);

				buffer.append("</div>\n");

				lastMessageId = message.getMessageId();
				previousMessage = message;
			}

			/* If we're at the point where we've displayed the first x messages and we're actually going to skip some messages,
			 * put the link to display them all in */

			if (justSkippedMessages) {
				appendDisplayAllMessagesLink(thread, showAllMessages, buffer, messages, messageCount, showAtStart, endMessagesStartAt, message, xPost);
			} else {
				justSkippedMessages = appendDisplayAllMessagesLink(thread, showAllMessages, buffer, messages, messageCount, showAtStart, endMessagesStartAt, message, xPost);
			}

			messageCount++;
		}

		/* If this thread is bookmarked, then put a link in to unbookmark it */

		if (user.getBookmarks().hasThreadBookmarked(thread.getBoardId(), thread.getThreadId())) {

			if (user.isLoggedIn()) {
				if (user.getPreferences().isUseTraditionalBookmarks() || mobileDevice) {
					buffer.append(" <a href=\"");
					buffer.append(Configuration.getInstance().getRoot());
					buffer.append("/Pages/confirmdeletebookmark.jsp?action=bookmark&amp;boardid=");
					buffer.append(thread.getBoardId());
					buffer.append("&amp;threadid=");
					buffer.append(thread.getThreadId());
					buffer.append("\">Remove Bookmark</a>");
				} else {
					String url = Configuration.getInstance().getRoot() + "/DeleteBookmarkControllerServlet";
					String parameters = "?boardid=" + thread.getBoardId() + "&amp;threadid=" + thread.getThreadId() + "&amp;sid=" + key;

					buffer.append(" <a class='bookmark' onClick=\"makeRequest('" + url + "', " + "'" + parameters + "'"
							+ "); setStyleByClass('div', 'unbookmarked', 'display', 'inline'); setTimeout('setStyleByClass(\\'div\\', \\'unbookmarked\\', \\'display\\', \\'none\\')', 1000); setTimeout('setStyleByClass(\\'div\\', \\'thisthread\\', \\'display\\', \\'none\\')', 1000); \">Remove Bookmark from this Thread</a>");
				}
			}
		}

		if (admin) {
			appendDeleteSelectedPosts(thread, buffer, user);
		}

		/* Set the last message pointer for this user to be the current last message. */

		user.setMostRecentMessageId(thread.getBoardId(), thread.getThreadId(), lastMessageId);

		buffer.append("</form>");
		
		return buffer.toString();
	}


	private static String hideImages(boolean mobileDevice, boolean admin, User user, Message message) {

		String body = message.getBody();

		body = ImageHider.substituteImagesForLinks(body, admin, mobileDevice, (!user.getPreferences().isShowImages()));

		return body;
	}


	private static void appendAdminLinks(Thread thread, StringBuilder buffer, boolean admin, User user, Message message) throws SQLException {

		if (admin) {
			boolean firstMessage = (thread.getFirstMessage().getMessageId() == message.getMessageId());
			MessageAdminLinksDisplay.includeAdminLinks(buffer, thread.getBoardId(), thread.getThreadId(), message.getMessageId(), firstMessage, message.getUserId(), message.getIpAddress(), user);
		}
	}


	private static void appendThreadRevivalIndicator(StringBuilder buffer, Message previousMessage, Message message, User user) {

		/* Calculate the time difference between the previous message and now, so as to insert a message explaining how much time
		 * has passed. */

		if (previousMessage != null) {

			long previousMessageTime = previousMessage.getTimestamp().getTime();
			long currentMessageTime = message.getTimestamp().getTime();

			if ((currentMessageTime - previousMessageTime) > TWO_WEEKS_IN_MILLISECONDS) {
				String timeDifference = DateFormatter.prettyPrintTimeDifference(previousMessage.getTimestamp(), message.getTimestamp(), false, true, true, user);
				buffer.append("<div class='threadrevival'>" + timeDifference + "...</div>");
			}
		}
	}


	private static void appendUnreadMessageLink(StringBuilder buffer, int mostRecentReadMessage, Message message) {
		if (message.getMessageId() == mostRecentReadMessage) {
			buffer.append("<a name='unread'></a>");
		}
	}


	private static void appendPermalink(StringBuilder buffer, Message message) {
		buffer.append("<a name='msg");
		buffer.append(message.getMessageId());
		buffer.append("'></a>");
	}


	private static boolean appendDisplayAllMessagesLink(Thread thread, boolean showAllMessages, StringBuilder buffer, Messages messages, int messageCount, int showAtStart, int endMessagesStartAt, Message message, boolean xPost) throws SQLException {

		/* If the gap between the start and the end is less than THRESHOLD then just show the messages, don't bother with the link */

		if (!xPost) {
			if ((messageCount == showAtStart) && (!showAllMessages) && (messageCount < endMessagesStartAt) && (overThreshold(showAtStart, endMessagesStartAt))) {
				int messagesSkipped = messages.size() - Configuration.getInstance().getOldestMessageCount() - Configuration.getInstance().getNewestMessageCount();
				buffer.append("<div class='morepostsmessage'><a href='" + Configuration.getInstance().getRoot() + "/ThreadSelectedControllerServlet?action=showall&amp;boardid=" + thread.getBoardId() + "&amp;threadid=" + thread.getThreadId() + "#msg" + message.getMessageId() + "'>Skipping " + messagesSkipped + " messages at this point... Click here if you want to load them all.</a></div>");
				return true;
			}
		}

		return false;
	}


	private static void appendPoll(String key, Thread thread, int bookmarkedMessageId, StringBuilder buffer, User user, boolean mobileDevice) throws SQLException {
		if (thread.isPoll() && (bookmarkedMessageId == 0)) {
			if (!thread.isPollClosed()) {

				if (thread.isLocked()) {
					appendLockedPoll(key, thread, buffer, user, mobileDevice);
				} else {
					appendPoll(key, thread, buffer, user, mobileDevice);
				}

			} else if (thread.isPollClosed()) {
				appendClosedPoll(key, thread, buffer, user, mobileDevice);
			}
		}
	}


	private static boolean displayThisMessage(boolean showAllMessages, boolean displayFromMostRecentlyRead, int messageCount, int showAtStart, int endMessagesStartAt) {

		return showAllMessages || (!overThreshold(showAtStart, endMessagesStartAt)) || ((messageCount < showAtStart) || (messageCount >= endMessagesStartAt));

	}


	private static void appendXPostWarning(int originalMessageCount, StringBuilder buffer, int messageCount) {
		if ((originalMessageCount > 0) && (messageCount == originalMessageCount)) {
			buffer.append("<a name='xpost'><div class='xpostwarning'><strong>Other messages have been posted since you last looked... Please review and if you want to change your message, do so before posting.</strong></div>");
		}
	}


	private static void appendSpecialFormatting(StringBuilder buffer, Message message) {
		if (message.getMessageId() == 1) {
			buffer.append("<div class='firstmessage'>");
		} else {
			if (message.isDeleted()) {
				/* Admins see deleted messages as scored out */
				buffer.append("<div class='deletedmessage'>");
			} else {
				buffer.append("<div class='message'>");
			}
		}
	}


	private static void appendDeleteSelectedPosts(Thread thread, StringBuilder buffer, User user) {
		buffer.append("<input name='boardId' value='");
		buffer.append(thread.getBoardId());
		buffer.append("' type='hidden' />");
		buffer.append("<input name='threadId' value='");
		buffer.append(thread.getThreadId());
		buffer.append("' type='hidden' />");
		buffer.append("<div class='adminlinks'><input type='submit' value='Delete Selected Posts' /></div>");
	}


	private static void appendShowAllMessagesLink(Thread thread, StringBuilder buffer, Messages messages) {
		String message = "Not all messages are displayed: ";
		buffer.append("<div class='morepostsmessageattop'>" + message + "<a href='" + Configuration.getInstance().getRoot() + "/ThreadSelectedControllerServlet?action=showall&amp;boardid=" + thread.getBoardId() + "&amp;threadid=" + thread.getThreadId() + "'>show all messages" + "</a> (" + messages.size() + " of them)</div>");
	}


	private static boolean displayShowAllMessagesLink(boolean showAllMessages, int bookmarkedMessageId, User user, Messages messages, int showAtStart, int endMessagesStartAt) {

		return ((!showAllMessages) && (overThreshold(showAtStart, endMessagesStartAt)) && (user.getRole() != Role.Anonymous) && (messages.size() > (Configuration.getInstance().getOldestMessageCount() + Configuration.getInstance().getNewestMessageCount()))) || bookmarkedMessageId > 1;
	}


	private static boolean overThreshold(int showAtStart, int endMessagesStartAt) {
		boolean overThreshold = ((endMessagesStartAt - showAtStart) >= MESSAGE_GAP_THRESHOLD);
		return overThreshold;
	}


	private static void appendBBCodeErrorContainer(String[] bbCodeErrorContainer, StringBuilder buffer) {
		buffer.append("<div class=\"bbcode_warning\"><p class=\"explanation\"><em><strong>There was an error parsing the BBcode in your post:<br />" + bbCodeErrorContainer[0] + "</strong></em></p>");
		buffer.append("<span class=\"error_pre\">" + Formatter.escapeHtml(bbCodeErrorContainer[1]) + "</span>");
		buffer.append("<span class=\"error_post\">" + Formatter.escapeHtml(bbCodeErrorContainer[2]) + "</span>");
		buffer.append("<p class=\"advice\">You may correct these errors or ignore them by checking the <i>ignore</i> box below.</p>");
	}


	private static void appendClosedPoll(String key, Thread thread, StringBuilder buffer, User user, boolean mobileDevice) throws SQLException {
		buffer.append("<div class=\"poll\">");
		buffer.append(PollResultsDisplay.display(key, thread, user, mobileDevice));
		buffer.append("</div>");
	}


	private static void appendPoll(String key, Thread thread, StringBuilder buffer, User user, boolean mobileDevice) throws SQLException {
		buffer.append("<div class=\"poll\">");
		buffer.append(PollDisplay.displayClosingDate(thread, user));
		buffer.append(PollDisplay.display(key, thread, user, mobileDevice));
		buffer.append("</div>");
	}


	private static void appendLockedPoll(String key, Thread thread, StringBuilder buffer, User user, boolean mobileDevice) throws SQLException {
		buffer.append("<div class=\"poll\">");
		buffer.append("<p style=\"text-align: center\"><strong>This poll has been locked by an administrator</strong></p>");
		buffer.append(PollDisplay.displayClosingDate(thread, user));
		buffer.append(PollDisplay.displayLocked(key, thread, user, mobileDevice));
		buffer.append("</div>");
	}


	private static void displayThreadAdminLinks(Thread thread, StringBuilder buffer, User user) throws SQLException {
		ThreadListAdminLinksDisplay.includeAdminLinks(buffer, thread.getBoardId(), thread.getThreadId(), user, "threadadminmenu", "menubutton");
	}


	public static void appendUserDetails(String key, StringBuilder buffer, Thread thread, Message message, User viewer, boolean mobileDevice) {

		buffer.append("<p>");

		/* Viewer might be null, as might not have logged in yet */

		if (viewer == null) {
			viewer = User.getAnonymousUser();
		}

		boolean loggedIn = (viewer.getRole() != Role.Anonymous);

		String tooltip = "Click here for more information about this user";

		buffer.append("<span class='posterinfo'>&#8213; <span class='name'><a title='");
		buffer.append(tooltip);
		buffer.append("' href='");
		buffer.append(Configuration.getInstance().getRoot());
		buffer.append("/ViewUserProfileControllerServlet?messageid=");
		buffer.append(message.getMessageId());
		buffer.append("&amp;boardid=");
		buffer.append(thread.getBoardId());
		buffer.append("&amp;threadid=");
		buffer.append(thread.getThreadId());
		buffer.append("'>");
		buffer.append(message.getDisplayName());
		buffer.append("</a></span>, <span class='date'>");
		buffer.append(DateFormatter.prettyPrint(message.getTimestamp(), viewer) + "</span>");

		if (Configuration.getInstance().isDisplayHumanTimes() && (!viewer.getPreferences().isHideTimeDisplay()) && (viewer.getPreferences().isDisplayRelativeTimes())) {
			buffer.append(" <em class='howlongago'>(");
			buffer.append(DateFormatter.prettyPrintTime(message.getTimestamp(), viewer, false, true));
			buffer.append(")</em>");
		}

		if (viewer.isBoardAdmin(thread.getBoardId())) {

			String ipAddress = message.getIpAddress();

			if ((ipAddress != null) && (!ipAddress.equals("0.0.0.0"))) {

				String location = "";

				if (viewer.isSiteAdmin() && Configuration.getInstance().isShowLocationDataOnMessagesForSiteAdmins()) {
					location = UserLocation.getLocation(ipAddress);
					if (location.length() > 0) {
						location = ": " + location;
					}
				}

				buffer.append(" <span class=\"ipaddress\">[IP: ");
				buffer.append(message.getIpAddress());
				buffer.append(location);
				buffer.append("]</span>");
			}
		}

		/* Bookmark link: might be AJAX, might not be, based on your preferences. */

		if (loggedIn) {
			if (viewer.getPreferences().isUseTraditionalBookmarks() || mobileDevice) {
				buffer.append(" <a href=\"" + Configuration.getInstance().getRoot() + "/Pages/confirmbookmark.jsp?action=bookmark&amp;boardid=" + thread.getBoardId() + "&amp;threadid=" + thread.getThreadId() + "&amp;messageid=" + message.getMessageId() + "\">Bookmark</a>");
			} else {
				String url = Configuration.getInstance().getRoot() + "/AddBookmarkControllerServlet";
				String parameters = "?boardid=" + thread.getBoardId() + "&amp;threadid=" + thread.getThreadId() + "&amp;messageid=" + message.getMessageId() + "&amp;sid=" + key;

				if (thread.getLatestMessageId() == message.getMessageId()) {
					buffer.append(" <a class='bookmark' onClick=\"makeRequest('" + url + "', " + "'" + parameters + "'" + "); setStyleByClass('div', 'bookmarked', 'display', 'inline'); setTimeout('setStyleByClass(\\'div\\', \\'bookmarked\\', \\'display\\', \\'none\\')', 1000); setTimeout('setStyleByClass(\\'div\\', \\'thisthread\\', \\'display\\', \\'none\\')', 1000); \">Bookmark</a>");
				} else {
					buffer.append(" <a class='bookmark' onClick=\"makeRequest('" + url + "', " + "'" + parameters + "'" + "); setStyleByClass('div', 'bookmarked', 'display', 'inline'); setTimeout('setStyleByClass(\\'div\\', \\'bookmarked\\', \\'display\\', \\'none\\')', 1000); \">Bookmark</a>");
				}
			}
		}

		buffer.append(" <span class=\"functions\">");

		if (loggedIn && Configuration.getInstance().isSupportSuggestBan()) {
			buffer.append("<span class=\"bookmark\"><a href=\"");
			buffer.append(Configuration.getInstance().getRoot());
			buffer.append("/Pages/confirmflagpost.jsp?action=confirmflag&amp;boardid=");
			buffer.append(thread.getBoardId());
			buffer.append("&amp;threadid=");
			buffer.append(thread.getThreadId());
			buffer.append("&amp;messageid=");
			buffer.append(message.getMessageId());
			buffer.append("\">Flag Post</a></span>");
		}

		buffer.append(" <span class=\"permalink\"><a href=\"");
		buffer.append(Configuration.getInstance().getRoot());
		buffer.append("/ThreadSelectedControllerServlet?showall=true&amp;bookmarkedmessageid=");
		buffer.append(message.getMessageId());
		buffer.append("&amp;boardid=");
		buffer.append(thread.getBoardId());
		buffer.append("&amp;threadid=");
		buffer.append(thread.getThreadId());
		buffer.append("\">Permalink</a></span>");

		buffer.append("</span></span>");

		buffer.append("</p>");

	}

}
